import os
import wx
import socket

import yaml

from . import plugins as pluginRoot
from . import phoneState

class Application(object):
    """Main application class.

    One that binds all others to a single app.

    """

    _mainConfigName = "main.cfg"
    _sysOpts = _logger = _mainConfig = None
    _activeSessionData = _connection = None
    wxApp = None
    _loadedPlugins = None
    _windows = phone = None

    def __init__(self, systemOptions, logger):
        self._sysOpts = systemOptions
        self._logger = logger
        self._loadedPlugins = {}
        self._windows = {}
        # set logger levels
        for _level in self._logger.levels:
            setattr(self, "log_" + _level,
                getattr(self._logger, "log_" + _level))
        self.readMainConfig()
        # init util modules
        import app.util
        app.util.initModule(self)

    def readMainConfig(self):
        """Read the main application config file to memory."""
        _configFile = os.path.join(
            self.getSysOpt("configDir"), self._mainConfigName)
        if os.path.exists(_configFile):
            self._mainConfig = yaml.load(file(_configFile))
        else:
            self._mainConfig = {}

    def saveMainConfig(self):
        """Save the main application config file from memory."""
        _configFile = os.path.join(
            self.getSysOpt("configDir"), self._mainConfigName)
        _file = file(_configFile, "w")
        yaml.dump(self._mainConfig, _file)
        _file.close()
        self.log_debug("Config saved")

    def readPluginConfig(self, name):
        """Read and return config file for plugin with given name."""
        _configFile = os.path.join(
            self.getSysOpt("configDir"), "plugin_%s.cfg" % name)
        if os.path.exists(_configFile):
            _rv = yaml.load(file(_configFile))
        else:
            _rv = {}
        return _rv

    def savePluginConfig(self, name, config):
        """Save configuration dict for plugin with given name."""
        _configFile = os.path.join(
            self.getSysOpt("configDir"), "plugin_%s.cfg" % name)
        yaml.dump(config, file(_configFile, "w"))
        self.log_debug("Config for plugin '%s' saved", name)

    def run(self):
        """Run application itself."""
        _app = wx.App()
        # ensure having my exception hooks, etc..
        self._logger.setSysHooks()
        self.wxApp = _app
        self.showMainWindow()
        _app.MainLoop()

    def _showWindow(self, cls, modal=False):
        """Show window of given calss with given modal(ity?) state."""
        _name = cls.__name__
        if not self._windows.get(_name, None):
            self._windows[_name] = cls(None, self)
        _obj = self._windows[_name]
        self.wxApp.SetTopWindow(_obj)
        if modal:
            _obj.ShowModal()
        else:
            _obj.Show(True)
        return _obj

    def showMainWindow(self):
        """Show main program window."""
        from .gui import main as mainForm
        self._showWindow(mainForm.MainWindow)

    def showConnectWindow(self, modal=True):
        """Show 'connect' window"""
        from .gui import login
        self._showWindow(login.LoginWindow, modal)

    def terminate(self):
        """Called upon program terminate."""
        self.saveMainConfig()
        if self._connection:
            self._connection.close()

    def getSysOpt(self, name):
        """Get system option value (if any)."""
        return self._sysOpts.get(name, None)

    def getConfigSection(self, name, defval=None):
        """Main application config section."""
        return self._mainConfig.get(name, defval)

    def getTmpDir(self):
        """Return temporary dir path."""
        return self._sysOpts["tmpDir"]

    def getTmpFileName(self):
        """Return random name for temporary file."""
        # Yes, I am aware of os.tempnam
        _dir = self.getTmpDir()
        _existing = os.listdir(_dir)
        _fid = 1
        while str(_fid) in _existing:
            _fid += 1
        return os.path.join(_dir, str(_fid))

    def setConfigSection(self, name, value):
        """Set main application config section to given value."""
        self._mainConfig[name] = value
        self.log_debug("Config section %s set to %s", name, value)

    def setActiveSession(self, session):
        """Set active SSH session stats: user, pass, host etc."""
        self._activeSessionData = session
        self._connection = None

    def _sshConnect(self):
        """Connect to SSH host."""
        from app.net import ssh
        self._connection = ssh.SshConnection(
            self._activeSessionData["host"],
            self._activeSessionData["port"],
            self._activeSessionData["user"],
            self._activeSessionData["password"]
        )
        self._connection.connect()
        self._onConnected()

    def _ensureConnection(self):
        """Ensure that we're connected to remote host."""
        if not self._connection:
            if not self._activeSessionData:
                self.showConnectWindow()
            if not self._activeSessionData:
                raise Exception("User denied to select connection options?")
            self._sshConnect()

    def showRemoteWait(self, sshPopen, settings={}):
        from .gui import remoteWait
        _win = remoteWait.RemoteWait(None, self)
        _win.attachToSshProcess(sshPopen, **settings)
        _win.Show()

    def executeRemote(self, args, showWaitWindow=False, waitWindowArgs={}):
        """Execute remote command.

        Return object mimicking subprocess.Popen
        (but with non-blocking I/O).

        """
        self._ensureConnection()
        if isinstance(args, basestring):
            _cmd = args
        else:
            _cmd = " ".join(args)
        self.log_info("Executing remote command '%s'", _cmd)
        _proc = self._connection.popen(_cmd)
        _proc.addWaitCallback(lambda: self.lockMainWindow(True))
        _proc.addDeathCallback(lambda: self.lockMainWindow(False))
        if showWaitWindow:
            self.showRemoteWait(_proc, waitWindowArgs)
        return _proc

    def saveFromRemote(self, args, fobj, waitWindowConfig={}):
        """Save data from remote call to given file-like object.

        (only STDOUT is being saved).
        """
        self._ensureConnection()
        if isinstance(args, basestring):
            _cmd = args
        else:
            _cmd = " ".join(args)
        self.log_info("Saving from remote command '%s'", _cmd)
        _proc = self._connection.popen(
            _cmd, autoStart=False, useDefaultStdout=False
        )
        _proc.addStdoutHandler(lambda data: fobj.write(data))
        self.lockMainWindow(True)
        _proc.addDeathCallback(lambda: self.lockMainWindow(False))
        self.showRemoteWait(_proc, waitWindowConfig)
        _proc.start()
        _proc.wait()

    def getActiveSession(self):
        return self._activeSessionData

    def getOpenedPlugins(self):
        """Return list of plugins to be opened."""
        _curPlugins = pluginRoot.getPluginList()
        _lastPluginData = self.getConfigSection("plugins", {
            "known": [],
            "opened": [],
        })
        _pluginsToOpen = set(_lastPluginData["opened"]).union(
            set(_curPlugins).difference(_lastPluginData["known"]))
        _pluginsToOpen = _pluginsToOpen.intersection(_curPlugins)
        self.setConfigSection("plugins", {
            "known": _curPlugins,
            "opened": list(_pluginsToOpen),
        })
        return sorted(_pluginsToOpen)

    def getPlugin(self, name, formParent):
        """Return plugin object."""
        if not self._loadedPlugins.get(name, None):
            _cls = pluginRoot.getPluginClass(name)
            _obj = _cls(formParent, self)
            self._loadedPlugins[name] = _obj
        return self._loadedPlugins[name]

    def lockMainWindow(self, status=True):
        """Forbid (or allow) any user interaction with progam."""
        from .gui import main as mainForm
        self._windows[mainForm.MainWindow.__name__].Enable(not status)

    def openRemotePort(self, port):
        """Open given port on remote host.

        Returns socket.

        """
        self._ensureConnection()
        _sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        _sock.connect((self._activeSessionData["host"], port))
        return _sock

    def _onConnected(self):
        """Method that is executed first when connected to phone."""
        self.phone = phoneState.PhoneMonitor(self)
        self.phone.fetchData()
        # disable all screensavers
        # (to prevent suspend)
        for _disp in self.phone.xDisplays:
            self.phone.tmpDisableXScreensaver(_disp)


# vim: set sts=4 sw=4 et :
